package com.aner.tester.resolver;

import com.aner.tester.constant.Constant;
import com.aner.tester.generator.param.ContentType;
import com.aner.tester.generator.param.ParameterFactory;
import com.aner.tester.object.ControllerObject;
import com.aner.tester.object.MethodObject;
import com.aner.tester.object.ValueObject;
import com.aner.tester.utils.text.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import com.aner.tester.utils.LoggerUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * method resolver
 *
 * @author aner
 * @version 1.0
 * @date 2020/12/14 15:26
 */
public class MethodResolver {

    private static Logger logger = LoggerUtils.getLogger(MethodResolver.class);

    private static Set<Class <? extends Annotation>> mappingSet = new HashSet<>();

    private static String REQUEST = "RequestMapping";

    static  {
        mappingSet.add(GetMapping.class);
        mappingSet.add(PostMapping.class);
        mappingSet.add(PutMapping.class);
        mappingSet.add(DeleteMapping.class);
        mappingSet.add(PatchMapping.class);
        mappingSet.add(RequestMapping.class);
    }

    public static MethodObject resolve(ControllerObject controllerObject, Method method, String classMappingValue) {
        MethodObject methodObject = new MethodObject();
        // get parameter information list
        List<ValueObject> valueObjectList = ParameterResolver.resolve(method);
        methodObject.setValueObjects(valueObjectList);
        // set method name
        methodObject.setName(method.getName());
        // set request data method(contained form-data and payload)
        methodObject.setContentType(getContentType(method.getParameters()));
        // set request mapping method
        String[] requestArray = getMethodMappingInfo(method);
        if (requestArray != null) {

            methodObject.setRequestMethod(requestArray[0]);
            // get full method mapping name
            String fullMappingValue = mergeMappingValue(classMappingValue, requestArray[1]);
            methodObject.setRequestValue(fullMappingValue);
        }
        String parameterValue = ParameterFactory.generate(methodObject);
        methodObject.setParameterValue(parameterValue);
        logger.info("Generating data for api {}-{}:[{}] {}", controllerObject.getClassName(), methodObject.getName(), methodObject.getRequestValue(), methodObject.getParameterValue());
        return methodObject;
    }

    public static String[] getMethodMappingInfo(Method method){
        Annotation[] annotations = method.getAnnotations();
        String mappingValue = Strings.EMPTY;
        String mappingName = Strings.EMPTY;
        for(Class mapping: mappingSet) {
            String simpleName = mapping.getSimpleName();
            for (Annotation annotation: annotations) {
                if (annotation.toString().contains(simpleName)) {
                    try {
                        InvocationHandler invocationHandler = Proxy.getInvocationHandler(method.getAnnotation(mapping));
                        Field fieldValue = invocationHandler.getClass().getDeclaredField("memberValues");
                        fieldValue.setAccessible(true);
                        Map<String, Object> memberValues = (Map<String, Object>) fieldValue.get(invocationHandler);
                        String[] values = (String[]) memberValues.get("value");

                        mappingValue = values.length > 0 ? values[0]: mappingValue;
                        mappingName = simpleName.substring(0, simpleName.length() - 7).toLowerCase();
                        if (REQUEST.equals(simpleName)) {
                            RequestMethod[] methods = (RequestMethod[]) memberValues.get("method");
                            // use the first value is if  requestMapping contains many values
                            if (StringUtils.isNotEmpty(methods)) {
                                mappingName = methods[0].name().toLowerCase();
                            }
                        }

                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    }catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                    return new String[]{mappingName,mappingValue};
                }
            }
        }

        return null;
    }

    /**
     * merge mapping value
     *
     * @param controllerMapping
     * @param methodMapping
     * @return
     */
    public static String mergeMappingValue(String controllerMapping, String methodMapping) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotEmpty(controllerMapping)) {
            if (!controllerMapping.startsWith(Constant.PATH_SLASH)) {
                sb.append(Constant.PATH_SLASH);
            }
            sb.append(controllerMapping);
            if (!controllerMapping.endsWith(Constant.PATH_SLASH)) {
                sb.append(Constant.PATH_SLASH);
            }
        } else {
            sb.append(Constant.PATH_SLASH);
        }
        if (StringUtils.isNotEmpty(methodMapping)) {
            if (methodMapping.startsWith(Constant.PATH_SLASH)) {
                sb.append(methodMapping.substring(1));
            } else {
                sb.append(methodMapping);
            }
        }
        return sb.toString();
    }
    private static int getContentType(Parameter[] parameters) {
        if(parameters != null && parameters.length > 0) {
            for(Parameter parameter: parameters) {
                if (parameter.isAnnotationPresent(RequestBody.class)) {
                    return ContentType.PAYLOAD;
                }
            }
        }
        return ContentType.FORM_DATA;
    }

}
